Skip to content

feat(copilot): add GitHub Copilot provider (via @github/copilot-sdk) - #7656

Closed
NSExceptional wants to merge 1 commit into
pingdotgg:mainfrom
NSExceptional:github-copilot-provider
Closed

feat(copilot): add GitHub Copilot provider (via @github/copilot-sdk)#7656
NSExceptional wants to merge 1 commit into
pingdotgg:mainfrom
NSExceptional:github-copilot-provider

Conversation

@NSExceptional

@NSExceptional NSExceptional commented Aug 20, 2026

Copy link
Copy Markdown

Draft / RFC. Opening this early for maintainer feedback on integration and conventions. It builds, typechecks, and passes its unit tests, and I've smoke-tested real turns against the live copilot CLI — but I'm not confident it hits every T3 convention (multi-surface, receipts, provider parity), so I'd love guidance on what to tighten before it's merge-ready.

What

Adds GitHub Copilot as a first-class provider, alongside Codex / Claude / Cursor / Grok / OpenCode. It's driven by GitHub's first-party @github/copilot-sdk, which spawns and drives the installed copilot runtime binary over its typed JSON-RPC protocol (RuntimeConnection.forStdio) — the same engine the Copilot CLI/IDE use. No extra runtime download; it reuses whatever copilot the user already has (e.g. Homebrew).

How

  • Provider / driver / adapter under apps/server/src/provider/, plus a small provider/sdk/ layer:
    • CopilotSdkClient — scoped Effect wrapper around CopilotClient (start on acquire, stop on release; one shared client per provider instance).
    • CopilotSdkModels — maps client.listModels() into per-model capabilities: reasoning effort from each model's supportedReasoningEfforts, and a context-window tier gated on the model's longContext billing block. Applied via SessionConfig / session.setModel.
    • CopilotSdkRuntimeEvents — translates SDK SessionEvents into the canonical ProviderRuntimeEvent stream.
  • Session lifecycle: per-thread CopilotSession, a callback→Effect event bridge (SDK is callback-based, unlike the async-iterable providers), permission requests wired into the existing approval flow, and send + session.idle turn handling.
  • Model discovery via client.listModels().
  • Git text generation (commit messages, PR content, branch names, thread titles) uses the SDK's one-shot sendAndWait.
  • Contracts: adds copilot.sdk.event / copilot.sdk.permission runtime sources and Copilot settings/model schemas; web wiring for the provider settings + model picker (reuses the generic option-descriptor UI).
  • Resolves the copilot binary to an absolute path before spawning (a GUI-launched app inherits a minimal PATH), and passes env only on the stdio connection (the SDK rejects env in both places).

Testing

  • tsgo typecheck clean (contracts / server / web); targeted lint clean.
  • Unit tests for the model→capability mapping, tunable resolution, and provider status parsing (74 provider + text-gen tests green).
  • End-to-end smoke test against copilot 1.0.80: createSession → streaming sendsession.idle, setModel with long_context, and the permission callback. listModels() returns per-model reasoning efforts / context tiers as expected.

Notes / open questions

  • Builds on earlier Copilot-provider groundwork by @its-hmny (credited via Co-authored-by); I ported it to the SDK and reworked model discovery + tunables.
  • The SDK is callback-based; I bridge its events into an Effect queue. Happy to align that with how you'd prefer provider adapters to consume SDK streams.
  • Mobile surface and docs aren't touched yet — guidance welcome on what's expected for a new provider.

Authored with Claude Opus 4.8 via Claude Code.

Note

Add GitHub Copilot provider via @github/copilot-sdk

  • Introduces a full copilot provider driver registered in builtInDrivers.ts, wiring together a SDK client, adapter, text generation, and provider snapshot pipeline.
  • CopilotSdkClient.ts wraps the SDK with Effect-based methods, binary path resolution, and scoped lifecycle management.
  • CopilotAdapter.ts manages per-thread sessions, streaming runtime events, permission requests, resume cursors, and in-session model switching.
  • CopilotTextGeneration.ts implements commit message, PR content, branch name, and thread title generation with schema-validated JSON and a 180s timeout.
  • CopilotProvider.ts probes the Copilot CLI for version/auth status and discovers models via the SDK, enriching snapshots asynchronously.
  • Contracts and UI are updated to recognize the copilot kind, default to gpt-4.1 / gpt-4.1-mini, and surface the provider in pickers and settings dialogs.
  • Risk: adds @github/copilot-sdk dependency and disallows koffi builds in pnpm-workspace.yaml; COPILOT_DRIVER_KIND defaults in model.ts may affect model selection if overrides are absent.
📊 Macroscope summarized 56d5f9a. 17 files reviewed, 16 issues evaluated, 2 issues filtered, 10 comments posted

🗂️ Filtered Issues

apps/server/src/provider/Layers/CopilotAdapter.ts — 3 comments posted, 5 evaluated, 1 filtered
  • line 447: The adapter constructs or accepts nativeEventLogger, but never invokes its write method. Therefore configuring nativeEventLogPath or injecting a native logger creates/manages the logger while every Copilot SDK event is silently omitted from the native event log. [ Out of scope (post-validation triage) ]
apps/server/src/provider/Layers/CopilotProvider.ts — 2 comments posted, 3 evaluated, 1 filtered
  • line 115: detectCopilotAuthFromEnvironment uses nullish coalescing before checking whether a token is nonempty. If COPILOT_GITHUB_TOKEN is defined as "" or whitespace while GH_TOKEN or GITHUB_TOKEN contains a valid token, the empty first value wins and the function returns unknown instead of authenticated. Select the first nonblank value rather than the first non-nullish one. [ Out of scope (post-validation triage) ]

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 097d2be5-d01a-4756-9d5f-7d821c71e49e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 20, 2026
Comment thread apps/server/src/provider/Drivers/CopilotDriver.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread packages/contracts/src/settings.ts
Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkRuntimeEvents.ts Outdated
const generateThreadTitle: TextGenerationShape["generateThreadTitle"] = Effect.fn(
"CopilotTextGeneration.generateThreadTitle",
)(function* (input) {
const { prompt, outputSchema } = buildThreadTitlePrompt({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium textGeneration/CopilotTextGeneration.ts:202

The Copilot provider ignores configured generation context: generateThreadTitle drops previousTitle, while generateBranchName, generatePrContent, and generateCommitMessage drop their respective policy values; generatePrContent also drops changeRequestTemplate. As a result, title regeneration uses the initial-title prompt, and branch names, PR content, and commit messages are generated without the caller's naming, template, or instruction constraints. Pass these omitted fields through to the corresponding build*Prompt calls.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/textGeneration/CopilotTextGeneration.ts around line 202:

The Copilot provider ignores configured generation context: `generateThreadTitle` drops `previousTitle`, while `generateBranchName`, `generatePrContent`, and `generateCommitMessage` drop their respective `policy` values; `generatePrContent` also drops `changeRequestTemplate`. As a result, title regeneration uses the initial-title prompt, and branch names, PR content, and commit messages are generated without the caller's naming, template, or instruction constraints. Pass these omitted fields through to the corresponding `build*Prompt` calls.

Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect Service Conventions — 6 findings

The new Copilot provider code mostly follows the repo's Effect conventions, but a few error-handling details in the new files diverge from them (and from the sibling Grok/Cursor/Aether implementations):

  • CopilotSdkError is a Data.TaggedError whose only payload is a stringified cause.
  • CopilotTextGeneration hand-rolls a _tag predicate, adds a pass-through error factory, and uses Effect.catchTag instead of Effect.catchTags.
  • CopilotDriver / CopilotAdapter fold cause.message into the caller-visible detail.

Details inline.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/provider/Drivers/CopilotDriver.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts Outdated
Comment thread apps/server/src/textGeneration/CopilotTextGeneration.ts Outdated
Comment thread apps/server/src/textGeneration/CopilotTextGeneration.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread apps/server/src/textGeneration/CopilotTextGeneration.ts
@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 56d5f9a to c10b8ef Compare August 20, 2026 09:40
@NSExceptional

Copy link
Copy Markdown
Author

Thanks for the automated review — addressed the findings (force-pushed c10b8ef). Typecheck + lint clean, 75 provider/text-gen tests green.

Correctness

  • CopilotAdapter consumer fiber (High) — forked with Effect.forkChild, so it was tied to the startSession fiber and interrupted on return. Now Effect.forkIn(adapterScope); torn down explicitly in stopSessionInternal.
  • Concurrent sendTurn (High) — a second turn overwrote activeTurnCompletion and misattributed events. Now rejects a second in-flight turn (A turn is already in progress) rather than serialize across the whole turn, since stopSession/interruptTurn must run mid-turn.
  • ServerSettingsPatch missing copilot (High) — added CopilotSettingsPatch and registered it in the patch provider map, so binaryPath/enabled persist.
  • Stale activeTurnId (Medium) — cleared both ctx.activeTurnId and the session's activeTurnId on completion so listSessions() doesn't report an idle session as active.
  • refreshInterval override (Medium) — removed; Copilot now follows providerHealthRefreshInterval like the other providers.
  • parseCopilotVersionOutput missing-binary (Medium) — the nonzero-exit branch now also matches not found/enoent, so the install guidance is surfaced (added a test).

Effect conventions

  • CopilotTextGeneration now uses Schema.is(TextGenerationError) and Effect.catchTags, and drops the pass-through error factory (matches CursorTextGeneration).
  • CopilotSdkError aligned to { operation, detail, cause } like OpenCodeRuntimeError; adapter errors use a static detail + cause instead of folding cause.message.

One I left as-is: the Failed to build Copilot snapshot: ${cause.message} detail in CopilotDriver — that mirrors GrokDriver's exact pattern, so I kept it consistent with the sibling. Happy to change if you'd prefer otherwise.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One outstanding convention issue remains in the Copilot SDK error-wrapping path. Earlier findings on CopilotTextGeneration.ts (redundant error factory, hand-rolled _tag predicate, catchTag) are addressed. The still-open threads on CopilotDriver.ts:160 and CopilotSdkClient.ts:32-45 also remain applicable.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkModels.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts
@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from c10b8ef to 45ca7b9 Compare August 20, 2026 09:46
@NSExceptional

Copy link
Copy Markdown
Author

Second pass on the re-review (45ca7b9):

  • Native event logging — the nativeEventLogger was accepted but never fed. Added a logNative helper (mirroring GrokAdapter) and now record each SDK session event and permission request/completion, so nativeEventLogPath / the injected logger capture Copilot traffic.
  • resolveCopilotSdkTunables validation — now validates reasoning effort against the allowed set (none/low/medium/high/xhigh/max) and drops boolean/invalid values, matching the context-tier path and the documented guarantee (added tests).
  • Adapter error details — the getClient / createSession mappings now use stable structural phrases ("Failed to start the Copilot SDK runtime client." / "Failed to create or resume the Copilot SDK session.") with the raw cause preserved, instead of copying the SDK failure string.

Typecheck + lint clean, 76 tests green.

Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
makeProviderMaintenanceCapabilities({
provider: DRIVER_KIND,
packageName: null,
updateExecutable: "copilot",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Drivers/CopilotDriver.ts:53

The update action ignores an instance's configured binaryPath, so custom or absolute Copilot installations are updated using the PATH-resolved copilot binary—or fail when copilot is not on PATH. makeStaticProviderMaintenanceResolver always returns the hard-coded executable from UPDATE; use a resolver that derives the update executable from the supplied binaryPath.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Drivers/CopilotDriver.ts around line 53:

The update action ignores an instance's configured `binaryPath`, so custom or absolute Copilot installations are updated using the PATH-resolved `copilot` binary—or fail when `copilot` is not on `PATH`. `makeStaticProviderMaintenanceResolver` always returns the hard-coded executable from `UPDATE`; use a resolver that derives the update executable from the supplied `binaryPath`.

Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated
@NSExceptional

NSExceptional commented Aug 20, 2026

Copy link
Copy Markdown
Author

Round 3 (1e7bc5722):

  • interruptTurn could strand a turn (High) — if abort() rejected, no session.idle arrived and sendTurn blocked forever. Now completes the active turn as aborted after abort() settles (idempotent, so a normal abort that emits session.idle just no-ops).
  • send rejection left a stale active turn (Medium) — the error path now clears activeTurnId and session.activeTurnId too, not just activeTurnCompletion.
  • Auth env precedence (Medium) — a blank COPILOT_GITHUB_TOKEN no longer masks a real GH_TOKEN/GITHUB_TOKEN; picks the first non-blank token in order (with a test).

Not changed — flagging as consistent-with-siblings rather than a Copilot-specific bug:

  • Update executable ignores binaryPath (CopilotDriver:53)makeStaticProviderMaintenanceResolver hard-codes the executable, but GrokDriver and ClaudeDriver do exactly the same (grok/claude). Deriving the update binary from a custom binaryPath looks like a repo-wide maintenance-resolver change rather than something to fix only for Copilot — happy to do it separately if you'd like it repo-wide.

Typecheck + lint clean, 77 tests green.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 45ca7b9 to 1e7bc57 Compare August 20, 2026 09:51
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
@NSExceptional

Copy link
Copy Markdown
Author

Round 4 (a91e1c58b): turn cleanup is now interruption-safe — moved the active-turn reset (activeTurnCompletion / activeTurnId / session.activeTurnId) into an Effect.ensuring finalizer around the send + Deferred.await. Previously an interrupted or failed sendTurn left activeTurnCompletion set, wedging the session (all later turns rejected as already-in-progress). Now it clears on success, failure, or interruption alike. Typecheck + lint clean, 533 provider tests green.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 1e7bc57 to a91e1c5 Compare August 20, 2026 09:57

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One retained convention issue in the new Copilot SDK layer: CopilotSdkError is still modeled with Data.TaggedError and its detail is a stringified cause. See the inline note. (The earlier findings in CopilotTextGeneration.ts and CopilotAdapter.ts look addressed.)

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts Outdated
@NSExceptional

Copy link
Copy Markdown
Author

Round 5 (6d40d6ef5): CopilotSdkError now follows the provider/Errors.ts conventionSchema.TaggedErrorClass with cause: Schema.Defect() and a message derived from structural attributes (operation + detail), matching ProviderAdapterRequestError et al. (was Data.TaggedError). Typecheck + lint clean, 77 tests green.

I believe that leaves only the CopilotDriver:53 update-executable note, which I've kept consistent with GrokDriver/ClaudeDriver (all static-executable resolvers) rather than diverging one provider — flagged above as a repo-wide follow-up if you want it.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from a91e1c5 to 6d40d6e Compare August 20, 2026 10:02
Comment thread apps/server/src/provider/sdk/CopilotSdkRuntimeEvents.ts Outdated
@NSExceptional

Copy link
Copy Markdown
Author

Round 6 (6cd5f28b6): tool calls now emit the canonical item.started on tool.execution_start (was item.updated), so consumers see the start of the lifecycle; progress → item.updated, completion → item.completed.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 6d40d6e to 6cd5f28 Compare August 20, 2026 10:07
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts
Comment thread apps/server/src/provider/sdk/CopilotSdkRuntimeEvents.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One retained convention issue in apps/server/src/provider/sdk/CopilotSdkClient.ts: the wrapper's message is still derived from a detail field that copies cause.message. The previously flagged detail interpolation in apps/server/src/provider/Drivers/CopilotDriver.ts:160 is also still open (existing comment).

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review of the changed web files (providerIconUtils.ts, AddProviderInstanceDialog.tsx, providerDriverMeta.ts, session-logic.ts).

Provider registration is consistent: the new copilot driver is added to PROVIDER_CLIENT_DEFINITIONS, PROVIDER_ICON_BY_PROVIDER and PROVIDER_OPTIONS, the stale githubCopilot "Coming Soon" entry is removed alongside its now-unused icon import, and GithubCopilotIcon uses the same fill-black dark:fill-white treatment as the other brand icons, so light/dark tone matches the existing provider rows. No primitive reconstruction, class-override, or CSS-ownership issues found.

One finding: unrelated re-wrapping in providerDriverMeta.ts (details inline).

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/settings/providerDriverMeta.ts Outdated
Comment thread apps/web/src/components/settings/providerDriverMeta.ts Outdated
@NSExceptional

Copy link
Copy Markdown
Author

Round 7 (525c76bda):

  • CopilotSdkError — dropped the detail field; message is now derived purely from the structural operation attribute, with the real failure preserved on cause (matches the provider/Errors.ts models).
  • Formatting — ran vp fmt across all touched files; the earlier ~80-col re-wraps in providerDriverMeta.ts, model.ts, settings.ts, builtInDrivers.ts, etc. are back to the 100-col default, so fmt:check is clean.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 6cd5f28 to 525c76b Compare August 20, 2026 10:12
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
@NSExceptional

NSExceptional commented Aug 20, 2026

Copy link
Copy Markdown
Author

All three Macroscope review checks are green now (Correctness, Effect Service Conventions, UI Consistency). The only remaining red check is Vercel – t3code-marketing ("Authorization required to deploy") — that's the org deploy gate for an external-fork PR, not something in this diff. Ready for a human look whenever you have a moment; happy to keep iterating on anything else.

^ Leave it to Claude to make my comments sound AI-generated as hell

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions: one new finding plus one previously-flagged issue still present.

  • apps/server/src/provider/Layers/CopilotProvider.ts:314 — arbitrary defect text interpolated into the caller-visible probe message (commented inline).
  • apps/server/src/provider/Drivers/CopilotDriver.ts:160ProviderDriverError.detail is still built from cause.message; message must be derived from stable structural attributes only (already flagged on an earlier revision, so not re-commented).

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts Outdated
Comment thread apps/server/src/provider/Layers/CopilotProvider.ts
@NSExceptional

Copy link
Copy Markdown
Author

Round 11 (3a935573e):

  • Version check no longer trusts arbitrary output (High) — a zero-exit copilot version whose output has no parseable version now reports warning with a diagnostic instead of ready, so a mis-pointed binaryPath isn't treated as a healthy install (test added).
  • Release force-stop (High) — scope closure now falls back to forceStop() if stop() rejects, matching the acquisition path, so a failed graceful shutdown can't leave the runtime child process alive.

Typecheck + lint clean.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 3c1e448 to 3a93557 Compare August 21, 2026 01:13
@NSExceptional

Copy link
Copy Markdown
Author

Round 12 (0b546b10a):

  • Stale turn.completed after stop/replace (Medium) — when a session is stopped or replaced mid-turn, sendTurn now returns without mutating the detached context or publishing a turn.completed (which would otherwise land after session.exited / a replacement's start events). Guarded on ctx.stopped, which stopSessionInternal already sets.
  • Health-check message (style) — dropped the interpolated defect text; the non-missing-binary branch is now the stable phrase the sibling providers use (the failure is preserved on the error itself).

Typecheck + lint clean, 534 provider tests green.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 3a93557 to 0b546b1 Compare August 21, 2026 01:19
@NSExceptional

Copy link
Copy Markdown
Author

Round 13 (21f5b95d6): health probe now resolves the binary like the SDK doesrunCopilotVersionCommand was passing binaryPath straight to ChildProcess.make, so a GUI-launched process with a restricted PATH reported an installed CLI as missing (even though sessions worked, since the SDK resolves it). Exposed the resolver as resolveCopilotBinaryPath and use it for the version check too. Typecheck + lint clean.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 0b546b1 to 21f5b95 Compare August 21, 2026 01:24
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts
@NSExceptional

Copy link
Copy Markdown
Author

Round 14 (0c26e20a9):

  • Binary resolver rejects directories (High)isExecutable now stats the candidate and requires a regular file, so a directory named copilot earlier in PATH can't shadow a real CLI later in PATH.
  • Tool item-type labeling (Medium)grep/find/glob (local code searches) are no longer mislabeled as web_search; only actual web fetch/search tools map there, everything else falls through to dynamic_tool_call.

Typecheck + lint clean.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 21f5b95 to 0c26e20 Compare August 21, 2026 01:28
@NSExceptional

Copy link
Copy Markdown
Author

Round 15 (92461c7f7): rollbackThread now rewinds the actual SDK conversation, not just the local turn list. It uses session.rpc.history.listRewindPoints() + rewind({ eventId, mode: "conversation" }) (rewind points sorted by timestamp for order-safety), and only mirrors the local ctx.turns once the backend rewind returns outcome: "success" — otherwise it fails rather than falsely reporting a rollback that didn't happen. Typecheck + lint clean, 534 provider tests green.

(Also in this push: binary resolver now rejects directories via stat().isFile(), and grep/find/glob tool calls are no longer mislabeled web_search.)

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 0c26e20 to 92461c7 Compare August 21, 2026 01:35
Semaphore.make(1).pipe(
Effect.map((semaphore) => {
const next = new Map(current);
next.set(threadId, semaphore);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/CopilotAdapter.ts:238

threadLocksRef retains a semaphore and the threadId for every thread ever passed to withThreadLock, so a long-running server leaks one map entry per historical thread even after sessions stop or startSession fails. Remove entries once no operation can use the thread lock, while preserving synchronization for concurrent operations.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CopilotAdapter.ts around line 238:

`threadLocksRef` retains a semaphore and the `threadId` for every thread ever passed to `withThreadLock`, so a long-running server leaks one map entry per historical thread even after sessions stop or `startSession` fails. Remove entries once no operation can use the thread lock, while preserving synchronization for concurrent operations.

@NSExceptional

Copy link
Copy Markdown
Author

Round 16 (aa37d5d35):

  • Tool item-type preserved across the lifecycle (Medium) — the classification is computed once at tool.execution_start and remembered per toolCallId, so progress/completion no longer collapse command_execution/file_change/mcp_tool_call into a generic dynamic_tool_call.
  • Text-gen no longer drops caller context (Medium) — commit-message/PR/thread-title generation now forward policy, changeRequestTemplate, and previousTitle (matching CursorTextGeneration).
  • Respect enableProviderUpdateChecks (Medium) — the driver adopted the makeProviderSnapshotSettingsSource pattern and forwards the setting to enrichProviderSnapshotWithVersionAdvisory, so version advisories/npm checks are skipped when the user disables update checks.
  • Snapshot error detail (style) — static structural phrase + cause (matches AetherDriver/OpenCode2Driver).

Typecheck + lint clean, 596 provider/text-gen tests green.

Two I'm leaving as consistent-with-siblings (not Copilot-specific), flagging rather than diverging one adapter:

  • threadLocksRef growth (CopilotAdapter:238)GrokAdapter and CursorAdapter use the identical per-thread semaphore map with no eviction; naive cleanup risks the lock races the map exists to prevent, so this is better solved repo-wide (e.g. ref-counted) than in one adapter.
  • Update executable ignores binaryPath (CopilotDriver:53)makeStaticProviderMaintenanceResolver hard-codes the executable in GrokDriver/ClaudeDriver too; happy to make it binaryPath-aware repo-wide if you'd like.

@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 92461c7 to aa37d5d Compare August 21, 2026 01:45
Comment thread apps/server/src/provider/Layers/CopilotProvider.ts Outdated
@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from aa37d5d to 636565e Compare August 21, 2026 07:42
@NSExceptional

Copy link
Copy Markdown
Author

Round 17 (636565e03) — the two findings raised after round 16, plus @NaveDanan's remaining parity notes:

  • Repeated model discovery on empty options (Medium, CopilotProvider.ts:409) — a discovered model with no tunable reasoning/context options left modelsNeedDiscovery stuck at true, so every enrichment cycle re-spawned an SDK client and re-ran listModels(). Discovery is the only source of non-custom models (fallback/initial snapshots carry none), so their presence alone now means discovery ran; an empty optionDescriptors is treated as a valid result rather than a retry signal.
  • Windows binary resolution (High, CopilotSdkClient.ts)resolveCopilotBinaryPath only probed the exact name, so a normal npm-installed copilot.cmd never resolved and the SDK got a bare copilot. It now probes PATHEXT suffixes (.CMD, …) on Windows (NodePath.sep as the lint-safe platform check) alongside the exact name. Added CopilotSdkClient.test.ts covering PATH resolution, the directory-shadow rejection, and bare-name fallback.
  • Text-gen model options (parity) — the git text-generation path now forwards the caller's selected reasoningEffort / contextTier (resolveCopilotSdkTunables(modelSelection.options)) into the one-shot SessionConfig, matching the interactive adapter and CursorTextGeneration. (policy / changeRequestTemplate / previousTitle were already forwarded in round 16.)

Typecheck + lint + fmt clean; 20 Copilot provider/SDK tests green.

On the one item I've left as-is: threadLocksRef (CopilotAdapter.ts:238) retaining a semaphore per historical thread is byte-identical to GrokAdapter and CursorAdapter (verified). Per @NaveDanan's "I would not block this PR on broader behavior that is already shared by other providers," I've kept it consistent with the siblings rather than diverging one adapter — a safe eviction needs the same treatment across all three (naive cleanup risks a lock race where a concurrent op recreates the semaphore and loses mutual exclusion). Happy to do it as a repo-wide follow-up if you'd prefer.

@NaveDanan — thanks again for the thorough parity pass; that list was exactly right. The one thing I haven't added is a dedicated adapter test harness for concurrent turns / interruption / startup failure: the sibling adapter tests drive a mock ACP subprocess, which doesn't apply to the SDK transport, so that needs a mock copilot-sdk runtime — flagging it as a follow-up rather than blocking this on it.

Comment thread apps/server/src/provider/Layers/CopilotProvider.ts
@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from 636565e to e38346a Compare August 21, 2026 09:05
@NSExceptional

NSExceptional commented Aug 21, 2026

Copy link
Copy Markdown
Author

Round 18 (e38346a9d) — the Macroscope finding on model discovery, plus several correctness fixes surfaced by an adversarial self-review of the diff before pushing.

The reported finding (CopilotProvider.ts model discovery)
Discovery had been running in background enrichment, so every status refresh reset models to the fallback [] and re-discovered later — dropping the catalog transiently, and permanently if discovery then failed. Moved discovery into checkCopilotProviderStatus (matching Cursor/Grok, which discover during the status check), and added a driver-scoped discoveredModelsRef that preserves the last-known-good catalog across a discovery timeout/empty/failure. Enrichment is now version-advisory only. This also retires round-17's "needs discovery" heuristic — the whole enrichment-discovery bug class is gone rather than patched.

Correctness fixes found while reviewing the above

  • Provider errors now surface (was log-only). A root session.error was previously only logged — it neither emitted a runtime.error nor failed the turn, so a failed turn either reported completed or hung on its Deferred.await. It now emits runtime.error{provider_error} + settles the turn failed (mirroring OpenCodeAdapter). Added a session.shutdown handler too (crash → transport_error + failed; routine → aborted), so a terminal event with no trailing session.idle can't wedge the thread. Sub-agent-scoped events (agentId set) never touch the root turn.
    • Note on rate_limit/eligibleForAutoSwitch: since T3 registers no auto-switch handler (and never sets continueOnAutoMode), the SDK auto-declines the switch and the turn produces no output — so these are treated as terminal (surfaced + failed) rather than silently completed. There's a comment marking the exact spot to revisit if an auto-switch handler is ever added.
  • Uninterruptible client.start() bounded. start() runs in acquireRelease's uninterruptible acquire, so an Effect-level timeout can't cut a stalled handshake — and with discovery now on the refresh path, a hung start would freeze the provider's single refresh permit. Bounded it with a JS-level Promise.race. The failure/release cleanup is also bounded: the SDK's stop() resolves-with-errors rather than rejecting on a hang, so the old .catch(() => forceStop()) never fired — now it races stop() against a 2s timer and hard-kills on timeout. Discovery uses an 8s start cap so a wedged start + cleanup stays within the ~10s discovery budget (interactive/text-gen keep the generous 15s default).
  • No runtime spawn for a broken CLI. Discovery now gates on parsed.status === "ready".

Deliberately not done: a blanket per-turn timeout on Deferred.await (it would kill legitimately long agentic turns; there's no SDK disconnect/process-death callback to hook instead — verified — so terminal-event handling is the mechanism). Flagging it in case you'd prefer a different backstop.

Typecheck (0 errors, no new warnings) + lint + fmt clean; 20 provider/SDK tests green.

Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Comment thread apps/server/src/provider/sdk/CopilotSdkClient.ts
Comment thread apps/server/src/provider/Layers/CopilotAdapter.ts Outdated
Adds GitHub Copilot as a first-class provider, driven by the first-party
`@github/copilot-sdk`. The SDK spawns and drives the installed `copilot`
runtime binary over its typed JSON-RPC protocol (`RuntimeConnection.forStdio`),
so no extra runtime download is required.

Highlights:
- Provider / driver / adapter under `apps/server/src/provider/` plus a small
  `provider/sdk/` layer:
  - `CopilotSdkClient` — scoped Effect wrapper around `CopilotClient`.
  - `CopilotSdkModels` — maps `client.listModels()` to per-model capabilities:
    reasoning effort from each model's `supportedReasoningEfforts`, and a
    context-window tier gated on the model's `longContext` billing block.
  - `CopilotSdkRuntimeEvents` — translates SDK `SessionEvent`s into the
    canonical `ProviderRuntimeEvent` stream.
- Session lifecycle: one shared client, per-thread `CopilotSession`, a
  callback→Effect event bridge, permission requests wired into the existing
  approval flow, and `send` + `session.idle` turn handling. Reasoning effort
  and context tier are applied via `SessionConfig` / `session.setModel`.
- Model discovery via `client.listModels()`.
- Git text generation (commit messages, PR content, branch names, thread
  titles) uses the SDK's one-shot `sendAndWait`.
- Contracts: `copilot.sdk.event` / `copilot.sdk.permission` runtime sources and
  Copilot settings/model schemas; web UI wiring for settings + model picker.
- Resolves the `copilot` binary to an absolute path before spawning (a GUI app
  inherits a minimal PATH), and passes env only on the stdio connection.

Co-authored-by: its-hmny <enea.guidi@n26.com>
@NSExceptional
NSExceptional force-pushed the github-copilot-provider branch from e38346a to e0795ed Compare August 21, 2026 09:46
@NSExceptional

Copy link
Copy Markdown
Author

Round 19 (e0795ed7d) — the three findings from the last automated pass.

  • Rejected turns no longer mutate the session (Medium). sendTurn now validates attachments and the prompt before calling applyModelSelection, so a turn rejected for an invalid attachment or empty prompt no longer leaves a new model/reasoning-effort applied to the live session.
  • Empty PATH component honored (High). resolveCopilotBinaryPath was dropping empty PATH components via .filter(Boolean), so a copilot reachable through a POSIX empty component (which means the current directory) went unfound and the bare name was handed to the SDK (which does no PATH lookup). Empty components now resolve to the CWD, evaluated lazily so a normal PATH never touches it.
  • Interrupt→new-turn race (High). The SDK's session.idle/abort events are session-scoped and carry no turn id, so a delayed terminal event from an interrupted turn could settle a newer turn's deferred. Fixed by draining rather than correlating:
    • session.idle is now the sole terminal settle point; the bare abort event no longer settles (an idle with aborted: true follows it).
    • interruptTurn no longer force-settles on a clean abort() ack — it lets the turn's own aborted session.idle settle it. Because the one-turn guard stays closed until that idle settles the turn, no later turn can be admitted while the terminal event is still in flight, so a session-scoped idle can never mis-settle a newer turn (and nothing needs to be swallowed). It only force-settles when abort() is not acknowledged (no idle guaranteed → avoids stranding the turn). A side benefit: a turn stays honestly "running" while an attached shell command the abort didn't kill finishes, instead of a premature completed.

The residuals are narrow and, I think, acceptable: an unacknowledged abort() only rejects on a dead connection (which drives session.shutdown/session.error instead of a stray idle), and a truly hung runtime that emits no terminal event leaves the turn honestly in-progress until the session is stopped (not a lying completed). Happy to revisit if you'd prefer a hard interrupt timeout.

Typecheck (0 errors, no new warnings) + lint + fmt clean; 20 provider/SDK tests green.

@jtstothard

Copy link
Copy Markdown

I opened a stacked follow-up PR: NSExceptional/t3code#1.

While testing the provider, I found two integration gaps:

  • Copilot skill and config discovery was enabled for the SDK session, but T3 did not publish the discovered skills to its provider snapshot, so /skills searched the workspace instead.
  • T3 listed Copilot slash commands but sent them through session.send; Copilot requires session.rpc.commands.invoke, so /skills was interpreted by the model as a normal prompt.

The follow-up adds skill metadata discovery, built-in command discovery, command invocation, command-result rendering, and focused regression tests. I tested /skills end-to-end through T3 after the change.

@t3dotgg

t3dotgg commented Aug 23, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Outside Copilot integration that competes with three other outside proposals and product-owned provider work.

@t3dotgg t3dotgg closed this Aug 23, 2026
@NSExceptional

Copy link
Copy Markdown
Author

@t3dotgg can you elaborate on what that means? Is this something only you or the core team will be able to implement? Or is it something you have in progress already?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants